package life.cqq.jwt.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import life.cqq.jwt.entity.User;
import life.cqq.jwt.entity.User.UserBuilder;
import life.cqq.jwt.exception.JwsTokenInvalidException;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * Created by Kim QQ.Cong on 2022/7/7 / 13:29
 *
 * @author: CongQingquan
 * @Description: JWT工具类(针对于JWT规范下的JWS类别)
 */
@DependsOn("springContextUtils")
@Component
public class JwsUtils {

    /**
     * 签名密匙（对于不同的签名算法有不同的意义）
     */
    private static String secretKeyString;

    /**
     * 过期时间
     */
    private static long expire;// = 1000 * 60 * 5;

    private static SecretKey secretKey;

    /**
     * Init
     */
    @PostConstruct
    public void init() {
        String secretKeyString = SpringContextUtils.getProperty("jwt.secretKeyString");
        String expire = SpringContextUtils.getProperty("jwt.expire");
        JwsUtils.secretKeyString = secretKeyString;
        JwsUtils.expire = Long.parseLong(expire);
        JwsUtils.secretKey = new SecretKeySpec(secretKeyString.getBytes(), SignatureAlgorithm.HS256.getJcaName());
    }

    /**
     * Sign by payload
     *
     * @param payload
     * @return
     */
    public static String signByPayload(@Nullable Map<String, Object> payload) {
        return sign(null, payload, expire);
    }

    public static String signByPayload(@Nullable Map<String, Object> payload, long expire) {
        return sign(null, payload, expire);
    }

    /**
     * Sign
     *
     * @param header
     * @param payload
     * @return
     */
    public static String sign(@Nullable Map<String, Object> header,
                              @Nullable Map<String, Object> payload,
                              long expire) {
        return Jwts.builder()
                .setHeader(createHeader(header))
                .setClaims(payload)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .signWith(secretKey)
                .compact();
    }

    /**
     * Token is valid (valid and no expired)
     * @param token
     * @return
     */
    public static boolean valid(String token) {
        try {
            parse(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Token is invalid
     * @param token
     * @return
     */
    public static boolean invalid(String token) {
        return !valid(token);
    }

    /**
     * Refresh token
     *
     * @param token token that need to refresh
     * @return
     */
    public static String refresh(String token) {
        return refresh(token, expire);
    }

    /**
     * Refresh token
     *
     * @param token token that need to refresh
     * @return
     */
    public static String refresh(String token, long expire) {
        Jwt<Header, Claims> parseRes = parse(token);
        return sign(parseRes.getHeader(), parseRes.getBody(), expire);
    }

    /**
     * Get payload
     *
     * @param token
     * @return
     */
    public static Claims getPayload(String token) {
        return getPayload(token, 0L);
    }

    /**
     * Get payload
     *
     * @param token
     * @param allowedClockSkewSeconds
     * @return
     */
    public static Claims getPayload(String token, long allowedClockSkewSeconds) {
        return getPayload(token, allowedClockSkewSeconds, Function.identity());
    }

    /**
     * Get payload
     *
     * @param token
     * @param allowedClockSkewSeconds
     * @param handelRetVal
     * @return
     */
    public static <R> R getPayload(String token, long allowedClockSkewSeconds, Function<Claims, R> handelRetVal) {
        return handelRetVal.apply(parse(token, allowedClockSkewSeconds).getBody());
    }

    /**
     * Parse token
     * @param token
     * @return
     */
    public static Jwt<Header, Claims> parse(String token) {
        return parse(token, 0L);
    }

    /**
     * Parse token
     * @param token
     * @param allowedClockSkewSeconds
     * @return
     */
    public static Jwt<Header, Claims> parse(String token, long allowedClockSkewSeconds) {
        try {
            return Jwts.parserBuilder().setSigningKey(JwsUtils.secretKey).setAllowedClockSkewSeconds(allowedClockSkewSeconds)
                    .build().parse(token);
        } catch (Exception e) {
            throw new JwsTokenInvalidException(e);
        }
    }

    /**
     * Create header
     *
     * @param header
     * @return
     */
    private static Map<String, Object> createHeader(@Nullable Map<String, Object> header) {
        Map<String, Object> finalHeader = new HashMap<>();
        finalHeader.put("typ", "JWT");
        finalHeader.put("alg", SignatureAlgorithm.HS256.getJcaName());
        if (header != null) {
            finalHeader.putAll(header);
        }
        return finalHeader;
    }

    /**
     * Generate token
     *
     * @param user
     * @return
     */
    public static String generateToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("account", user.getAccount());
        claims.put("name", user.getName());
        return signByPayload(claims);
    }

    /**
     * Get user
     *
     * @return
     */
    public static User getUser() {
        String token = HttpRequestContext.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
        return getPayload(token, 0L, claims -> {
            UserBuilder builder = User.builder();
            Object account = claims.get("account");
            if (account instanceof String) {
                User.builder().account(account.toString());
            }
            Object name = claims.get("name");
            if (account instanceof String) {
                User.builder().account(name.toString());
            }
            return builder.build();
        });
    }

    /**
     * Token 解析器
     */
//    @Getter
//    public enum TokenParser {
//        /**
//         * 有效
//         */
//        VALID,
//        /**
//         * 无效(无法通过密匙解析)
//         */
//        INVALID,
//        /**
//         * 过期: Now > Token.Payload.exp && 时间差值 > Parser.allowedClockSkewSeconds
//         * <p>
//         * 若当前时间已大于Token的Payload中的Expiration字段标识的过期时间戳
//         * 且过期时间量已经超过parserBuilder().setAllowedClockSkewSeconds(n)的参数n秒时
//         */
//        EXPIRED;
//
//        @Setter
//        private Claims claims;
//
//        TokenParser() {
//        }
//
//        TokenParser(Claims claims) {
//            this.claims = claims;
//        }
//
//        /**
//         * Parse token
//         *
//         * @param token
//         * @return
//         */
//        public static TokenParser parse(String token) {
//            return parse(token, 0L);
//        }
//
//        /**
//         * Parse token
//         *
//         * @param token
//         * @param allowedClockSkewSeconds
//         * @return
//         */
//        public static TokenParser parse(String token, long allowedClockSkewSeconds) {
//            Claims claims;
//            try {
//                claims = Jwts.parserBuilder().setSigningKey(JwsUtils.secretKey).setAllowedClockSkewSeconds(allowedClockSkewSeconds)
//                        .build().parseClaimsJwt(token).getBody();
//            } catch (ExpiredJwtException e) {
//                return TokenParser.EXPIRED;
//            } catch (UnsupportedJwtException | IllegalArgumentException |
//                     SignatureException | MalformedJwtException e) {
//                return TokenParser.INVALID;
//            }
//            TokenParser returnVal = TokenParser.VALID;
//            returnVal.claims = claims;
//            return returnVal;
//        }
//    }
}